winsafe\kernel\utilities/path.rs
1//! File path utilities.
2//!
3//! Some of the functions are similar to [`std::path::Path`] ones, but here they
4//! work directly upon [`&str`](str) instead of [`&OsStr`](std::ffi::OsStr).
5
6use crate::co;
7use crate::decl::*;
8use crate::kernel::iterators::*;
9use crate::prelude::*;
10
11/// Returns an iterator over the files and folders within a directory. Does not
12/// search recursively.
13///
14/// This is a high-level abstraction over [`HFINDFILE`](crate::HFINDFILE)
15/// iteration functions.
16///
17/// # Examples
18///
19/// ```no_run
20/// use winsafe::{self as w, prelude::*};
21///
22/// // Ordinary for loop
23/// for file_path in w::path::dir_list_flat("C:\\Temp") {
24/// let file_path = file_path?;
25/// println!("{}", file_path);
26/// }
27///
28/// // Collecting into a Vec
29/// let all = w::path::dir_list_flat("C:\\Temp")
30/// .collect::<w::SysResult<Vec<_>>>()?;
31///
32/// // Filtering by extension and collecting into a Vec
33/// let txts = w::path::dir_list_flat("C:\\Temp")
34/// .filter(|file_path| match file_path {
35/// Err(_) => true,
36/// Ok(file_path) => w::path::has_extension(file_path, &["txt"]),
37/// })
38/// .collect::<w::SysResult<Vec<_>>>()?;
39///
40/// // Transforming and collecting into a Vec
41/// let all = w::path::dir_list_flat("C:\\Temp")
42/// .map(|file_path| {
43/// let file_path = file_path?;
44/// Ok(format!("PATH: {}", file_path))
45/// })
46/// .collect::<w::SysResult<Vec<_>>>()?;
47/// # w::SysResult::Ok(())
48/// ```
49#[must_use]
50pub fn dir_list_flat<'a>(dir_path: &'a str) -> impl Iterator<Item = SysResult<String>> + 'a {
51 DirListFlatIter::new(dir_path.to_owned())
52}
53
54/// Returns an interator over the files within a directory, and all its
55/// subdirectories, recursively.
56///
57/// This is a high-level abstraction over [`HFINDFILE`](crate::HFINDFILE)
58/// iteration functions.
59///
60/// # Examples
61///
62/// ```no_run
63/// use winsafe::{self as w, prelude::*};
64///
65/// // Ordinary for loop
66/// for file_path in w::path::dir_list_recursive("C:\\Temp") {
67/// let file_path = file_path?;
68/// println!("{}", file_path);
69/// }
70///
71/// // Closure with try_for_each
72/// w::path::dir_list_recursive("C:\\Temp")
73/// .try_for_each(|file_path| {
74/// let file_path = file_path?;
75/// println!("{}", file_path);
76/// Ok(())
77/// })?;
78///
79/// // Collecting into a Vec
80/// let all = w::path::dir_list_recursive("C:\\Temp")
81/// .collect::<w::SysResult<Vec<_>>>()?;
82///
83/// // Transforming and collecting into a Vec
84/// let all = w::path::dir_list_recursive("C:\\Temp")
85/// .map(|file_path| {
86/// let file_path = file_path?;
87/// Ok(format!("PATH: {}", file_path))
88/// })
89/// .collect::<w::SysResult<Vec<_>>>()?;
90/// # w::SysResult::Ok(())
91/// ```
92#[must_use]
93pub fn dir_list_recursive<'a>(dir_path: &'a str) -> impl Iterator<Item = SysResult<String>> + 'a {
94 DirListRecursiveIter::new(dir_path.to_owned())
95}
96
97/// Returns a new string with the path of the current EXE file, without the EXE
98/// filename, and without a trailing backslash.
99///
100/// In a debug build, the `target\debug` folders will be suppressed.
101#[cfg(debug_assertions)]
102#[must_use]
103pub fn exe_path() -> SysResult<String> {
104 let dbg = HINSTANCE::NULL.GetModuleFileName()?;
105 Ok(get_path(
106 get_path(
107 get_path(&dbg).unwrap(), // exe name; go up target and debug folders
108 )
109 .unwrap(),
110 )
111 .unwrap()
112 .to_owned())
113}
114
115/// Returns a new string with the path of the current EXE file, without the EXE
116/// filename, and without a trailing backslash.
117///
118/// In a debug build, the `target\debug` folders will be suppressed.
119#[cfg(not(debug_assertions))]
120#[must_use]
121pub fn exe_path() -> SysResult<String> {
122 Ok(get_path(&HINSTANCE::NULL.GetModuleFileName()?)
123 .unwrap()
124 .to_owned())
125}
126
127/// Returns true if the path exists.
128#[must_use]
129pub fn exists(full_path: &str) -> bool {
130 GetFileAttributes(full_path).is_ok()
131}
132
133/// Extracts the file name from a full path, if any.
134///
135/// # Examples
136///
137/// ```no_run
138/// use winsafe::{self as w, prelude::*};
139///
140/// let f = w::path::get_file_name("C:\\Temp\\foo.txt"); // foo.txt
141/// ```
142#[must_use]
143pub fn get_file_name(full_path: &str) -> Option<&str> {
144 match full_path.rfind('\\') {
145 None => Some(full_path), // if no backslash, the whole string is the file name
146 Some(idx) => {
147 if idx == full_path.chars().count() - 1 {
148 None // last char is '\\', no file name
149 } else {
150 Some(&full_path[idx + 1..])
151 }
152 },
153 }
154}
155
156/// Extracts the full path, but the last part.
157///
158/// # Examples
159///
160/// ```no_run
161/// use winsafe::{self as w, prelude::*};
162///
163/// let p = w::path::get_path("C:\\Temp\\xx\\a.txt"); // C:\Temp\xx
164/// let q = w::path::get_path("C:\\Temp\\xx\\"); // C:\Temp\xx
165/// let r = w::path::get_path("C:\\Temp\\xx"); // C:\Temp"
166/// ```
167#[must_use]
168pub fn get_path(full_path: &str) -> Option<&str> {
169 full_path
170 .rfind('\\') // if no backslash, the whole string is the file name, so no path
171 .map(|idx| &full_path[0..idx])
172}
173
174/// Tells whether the full path ends in one of the given extensions,
175/// case-insensitive.
176///
177/// # Examples
178///
179/// ```no_run
180/// use winsafe::{self as w, prelude::*};
181///
182/// println!("{}",
183/// w::path::has_extension("file.txt", &["txt", "bat"]));
184/// ```
185#[must_use]
186pub fn has_extension(full_path: &str, extensions: &[impl AsRef<str>]) -> bool {
187 let full_path_u = full_path.to_uppercase();
188 extensions
189 .iter()
190 .find(|ext| {
191 let mut ext_upper = ext.as_ref().to_uppercase();
192 if !ext_upper.starts_with('.') {
193 ext_upper.insert(0, '.'); // prepend dot
194 }
195 full_path_u.ends_with(&ext_upper)
196 })
197 .is_some()
198}
199
200/// Returns true if the path is a directory. Calls
201/// [`GetFileAttributes`](crate::GetFileAttributes).
202///
203/// # Panics
204///
205/// Panics if the path does not exist.
206#[must_use]
207pub fn is_directory(full_path: &str) -> bool {
208 let flags = GetFileAttributes(full_path).unwrap();
209 flags.has(co::FILE_ATTRIBUTE::DIRECTORY)
210}
211
212/// Returns true if the path is hidden. Calls
213/// [`GetFileAttributes`](crate::GetFileAttributes).
214///
215/// # Panics
216///
217/// Panics if the path does not exist.
218#[must_use]
219pub fn is_hidden(full_path: &str) -> bool {
220 let flags = GetFileAttributes(full_path).unwrap();
221 flags.has(co::FILE_ATTRIBUTE::HIDDEN)
222}
223
224/// Replaces the file extension by the given one, returning a new string.
225///
226/// # Examples
227///
228/// ```no_run
229/// use winsafe::{self as w, prelude::*};
230///
231/// let p = w::path::replace_extension(
232/// "C:\\Temp\\something.txt", ".sh"); // C:\Temp\something.sh
233/// ```
234#[must_use]
235pub fn replace_extension(full_path: &str, new_extension: &str) -> String {
236 if let Some(last) = full_path.chars().last() {
237 if last == '\\' {
238 return rtrim_backslash(full_path).to_owned(); // full_path is a directory, do nothing
239 }
240 }
241
242 let new_has_dot = new_extension.chars().next() == Some('.');
243 match full_path.rfind('.') {
244 None => format!(
245 "{}{}{}", // file name without extension, just append it
246 full_path,
247 if new_has_dot { "" } else { "." },
248 new_extension,
249 ),
250 Some(idx) => {
251 format!("{}{}{}", &full_path[0..idx], if new_has_dot { "" } else { "." }, new_extension,)
252 },
253 }
254}
255
256/// Replaces the file name by the given one, returning a new string.
257#[must_use]
258pub fn replace_file_name(full_path: &str, new_file: &str) -> String {
259 match get_path(full_path) {
260 None => new_file.to_owned(),
261 Some(path) => format!("{}\\{}", path, new_file),
262 }
263}
264
265/// Keeps the file name and replaces the path by the given one, returning a new
266/// string.
267///
268/// # Examples
269///
270/// ```no_run
271/// use winsafe::{self as w, prelude::*};
272///
273/// let p = w::path::replace_path( // C:\another\foo.txt
274/// "C:\\Temp\\foo.txt",
275/// "C:\\another",
276/// );
277/// ```
278#[must_use]
279pub fn replace_path(full_path: &str, new_path: &str) -> String {
280 let file_name = get_file_name(full_path);
281 format!(
282 "{}{}{}",
283 rtrim_backslash(new_path),
284 if file_name.is_some() { "\\" } else { "" },
285 file_name.unwrap_or("")
286 )
287}
288
289/// Removes a trailing backslash, if any.
290///
291/// # Examples
292///
293/// ```no_run
294/// use winsafe::{self as w, prelude::*};
295///
296/// let p = w::path::rtrim_backslash("C:\\Temp\\"); // C:\Temp
297/// ```
298#[must_use]
299pub fn rtrim_backslash(full_path: &str) -> &str {
300 match full_path.chars().last() {
301 None => full_path, // empty string
302 Some(last_ch) => {
303 if last_ch == '\\' {
304 let mut chars = full_path.chars();
305 chars.next_back(); // remove last char
306 chars.as_str()
307 } else {
308 full_path // no trailing backslash
309 }
310 },
311 }
312}
313
314/// Returns a `Vec` with each part of the full path.
315#[must_use]
316pub fn split_parts(full_path: &str) -> Vec<&str> {
317 let no_bs = rtrim_backslash(full_path);
318 no_bs.split('\\').collect()
319}